package com.shopping.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.shopping.common.to.es.SkuEsModel;
import com.shopping.search.config.ShoppingElasticSearchConfig;
import com.shopping.search.constant.EsConstant;
import com.shopping.search.service.MallSearchService;
import com.shopping.search.vo.SearchParamVo;
import com.shopping.search.vo.SearchResultVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Europa
 */
@Service("mallSearchService")
public class MallSearchServiceImpl implements MallSearchService {

    @Resource
    private RestHighLevelClient client;

    @Override
    public SearchResultVo search(SearchParamVo param) {
        // 动态构建出查询的DSL语句
        SearchResultVo result = null;
        //准备检索请求
        SearchRequest request = buildSearchRequest(param);
        try {
            //
            SearchResponse response = client.search(request, ShoppingElasticSearchConfig.COMMON_OPTIONS);

            result = buildSearchResult(response, param);
            System.out.println("result = " + JSON.toJSONString(result));
        } catch (IOException exception) {
            exception.printStackTrace();
        }
        return result;
    }

    /**
     * 构建结果数据
     *
     * @param response
     * @return
     */
    private SearchResultVo buildSearchResult(SearchResponse response, SearchParamVo param) {
        SearchResultVo vo = new SearchResultVo();
        // 处理查询结果
        SearchHits hits = response.getHits();
        SearchHit[] searchHits = hits.getHits();
        if (searchHits != null && searchHits.length > 0) {
            ArrayList<SkuEsModel> esList = new ArrayList<>(searchHits.length);
            for (SearchHit hit : searchHits) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSONObject.parseObject(sourceAsString, SkuEsModel.class);
                if (StringUtils.isNotEmpty(param.getKeyword())) {
                    HighlightField highlightField = hit.getHighlightFields().get("skuTitle");
                    String skuTitle = highlightField.getFragments()[0].toString();
                    skuEsModel.setSkuTitle(skuTitle);
                }
                esList.add(skuEsModel);
            }
            vo.setProducts(esList);
        } else {
            vo.setProducts(new ArrayList<>());
        }

        Aggregations aggregations = response.getAggregations();
        //商品涉及的所有属性
        ParsedNested attrAgg = aggregations.get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        ArrayList<SearchResultVo.AttrVo> attrVos = new ArrayList<>(attrIdAggBuckets.size());
        attrIdAggBuckets.forEach(bucket -> {
            SearchResultVo.AttrVo attrVo = new SearchResultVo.AttrVo();
            // 获得属性id
            long attrId = bucket.getKeyAsNumber().longValue();
            // 获得属性名字
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            // 获得属性的所有值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<? extends Terms.Bucket> attrValueAggBuckets = attrValueAgg.getBuckets();
            List<String> attrValueList = new ArrayList<>(attrValueAggBuckets.size());
            attrValueAggBuckets.forEach(item -> {
                String attrValue = item.getKeyAsString();
                attrValueList.add(attrValue);
            });

            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrValueList);
            attrVos.add(attrVo);
        });
        vo.setAttrs(attrVos);
        //商品涉及的所有品牌信息
        ParsedLongTerms brandAgg = aggregations.get("brand_agg");
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
        ArrayList<SearchResultVo.BrandVo> brandVos = new ArrayList<>(brandAggBuckets.size());
        brandAggBuckets.forEach(bucket -> {
            SearchResultVo.BrandVo brandVo = new SearchResultVo.BrandVo();
            // 获得品牌id
            long brandId = bucket.getKeyAsNumber().longValue();
            Aggregations bucketAggregations = bucket.getAggregations();
            // 获得品牌名
            ParsedStringTerms brandNameAgg = bucketAggregations.get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            // 获得品牌图片
            ParsedStringTerms brandImgAgg = bucketAggregations.get("brand_img_agg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            // 设置属性
            brandVo.setBrandId(brandId);
            brandVo.setBrandName(brandName);
            brandVo.setBrandImg(brandImg);
            brandVos.add(brandVo);
        });
        vo.setBrands(brandVos);

        //商品涉及的所有分类信息
        ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
        List<? extends Terms.Bucket> catalogAggBuckets = catalogAgg.getBuckets();
        List<SearchResultVo.CatalogVo> catalogVos = new ArrayList<>(catalogAggBuckets.size());
        for (Terms.Bucket bucket : catalogAggBuckets) {
            SearchResultVo.CatalogVo catalogVo = new SearchResultVo.CatalogVo();
            //得到分类id
            String keyAsString = bucket.getKeyAsString();
            catalogVo.setCatalogId(Long.parseLong(keyAsString));

            //得到分类名
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);
            catalogVos.add(catalogVo);
        }
        vo.setCatalogs(catalogVos);

        //页码
        vo.setPageNum(param.getPageNum());
        //总记录数
        long total = hits.getTotalHits().value;
        vo.setTotal(total);
        //总页码
        long totalPage = total / EsConstant.PRODUCT_PAGE_SIZE;
        if (total / EsConstant.PRODUCT_PAGE_SIZE != 0) {
            totalPage += 1;
        }
        vo.setTotalPages(totalPage);
        return vo;
    }

    /**
     * 准备检索请求
     *
     * @param paramVo
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParamVo paramVo) {
        String[] strings = {EsConstant.PRODUCT_INDEX};

        SearchSourceBuilder builder = new SearchSourceBuilder();

        // 1. 构建bool - query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.1 bool - must模糊匹配
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", paramVo.getKeyword()));
        }
        // 1.2.0 bool - filter 按照三级分类id查询
        if (paramVo.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", paramVo.getCatalog3Id()));
        }
        // 1.2.1 bool - filter 按照品牌id查询
        if (!CollectionUtils.isEmpty(paramVo.getBrandId())) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", paramVo.getBrandId()));
        }
        // 1.2.2 bool - filter 按照所有指定的属性查询
        if (!CollectionUtils.isEmpty(paramVo.getAttrs())) {
            paramVo.getAttrs().forEach(attr -> {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] s = attr.split("_");
                String attrId = s[0];
                String[] attrVal = s[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", attrVal));
                // 每一个都必须生成nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            });
        }
        // 1.2.3 bool - filter 按照有无库存查询
        Integer hasStock = paramVo.getHasStock();
        if (hasStock != null && hasStock == 1) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", true));
        }
        // 1.2.4 bool - filter 按照价格区间
        if (!StringUtils.isEmpty(paramVo.getSkuPrice())) {
            // 数据格式 1_200 _200 200_
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] s = paramVo.getSkuPrice().split("_");
            // 如果是'_'开头那么s的长度也是2,所以先判断'_'的位置
            if (paramVo.getSkuPrice().startsWith("_")) {
                rangeQuery.lte(s[1]);
            } else if (paramVo.getSkuPrice().endsWith("_")) {
                rangeQuery.gte(s[0]);
            } else {
                rangeQuery.gte(s[0]).lte(s[1]);
            }
        }
        builder.query(boolQuery);

        // 2. 排序 分页 高亮
        // 2.1 排序
        if (!StringUtils.isEmpty(paramVo.getSort())) {
            String sort = paramVo.getSort();
            String[] s = sort.split("_");
            SortOrder order = SortOrder.fromString(s[1]);
            builder.sort(s[0], order);
        }
        // 2.2 分页
        int from = 0;
        if (paramVo.getPageNum() != null) {
            from = (paramVo.getPageNum() - 1) * EsConstant.PRODUCT_PAGE_SIZE;
        }
        builder.from(from);
        builder.size(EsConstant.PRODUCT_PAGE_SIZE);
        // 2.3 高亮
        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            HighlightBuilder highlight = new HighlightBuilder();
            highlight.field("skuTitle");
            highlight.preTags("<b style='color: red'>");
            highlight.postTags("</b>");
            builder.highlighter(highlight);
        }

        // 3. 聚合分析
        // 3.1 品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg");
        brandAgg.field("brandId").size(50);

        // 3.1.1 品牌聚合的名字和图片
        TermsAggregationBuilder brandName = AggregationBuilders
                .terms("brand_name_agg")
                .field("brandName")
                .size(1);
        brandAgg.subAggregation(brandName);

        TermsAggregationBuilder brandImg = AggregationBuilders
                .terms("brand_img_agg")
                .field("brandImg")
                .size(1);
        brandAgg.subAggregation(brandImg);

        builder.aggregation(brandAgg);

        // 4. 分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders
                .terms("catalog_agg")
                .field("catalogId")
                .size(2);
        TermsAggregationBuilder catalogName = AggregationBuilders
                .terms("catalog_name_agg")
                .field("catalogName")
                .size(1);
        catalogAgg.subAggregation(catalogName);

        builder.aggregation(catalogAgg);

        // 5. 属性聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", "attrs");
        // 5.1 所有的attrId
        TermsAggregationBuilder attrIdAgg = AggregationBuilders
                .terms("attr_id_agg")
                .field("attrs.attrId")
                .size(5);
        // 5.2 当前attrId对应的attrName
        TermsAggregationBuilder attrNameAgg = AggregationBuilders
                .terms("attr_name_agg")
                .field("attrs.attrName")
                .size(1);
        attrIdAgg.subAggregation(attrNameAgg);
        // 5.3 当前attrId对应的所有可能的attrValue
        TermsAggregationBuilder attrValueAgg = AggregationBuilders
                .terms("attr_value_agg")
                .field("attrs.attrValue")
                .size(50);
        attrIdAgg.subAggregation(attrValueAgg);

        attrAgg.subAggregation(attrIdAgg);

        builder.aggregation(attrAgg);

        System.out.println("SearchSourceBuilder = " + builder.toString());
        return new SearchRequest(strings, builder);
    }
}
